D3 动画
D3.js提供了多种工具支持数据可视化的交互,其中d3.transition
让简单而高效的为图像添加动画成为了可能。
单单从API来讲,d3.transition
非常简单,用法类似Jquery。 但是想要设计出理想的动画效果,就不得不提到D3绘制图形的一个核心概念General Update Pattern
. D3的数据驱动特性的核心和实现就是依靠这个Pattern,而动画和交互自然要从它说起了。
并不是所有图形都必须遵循Update Pattern,比如一次性绘图,无交互的静态图形等。但如果涉及到了动态数据,这个Update Pattern不仅利于写出易于维护的代码,也能更好的发挥D3强大的功能。
General Update Pattern
D3的数据驱动模式如上图所示,当使用d3.data()
将数据Array
与DOM元素绑定的时,数据与元素之间有着三个阶段,即
- Enter 已有数据,但页面还未有与之对应的DOM
- Update 数据元素与DOM元素相绑定
- Exit 数据元素已经被删除,但DOM元素还存在,即失去了绑定元素的DOM
关于这个点,这里不做详细赘述,可参考文档。这里直接对V4和V5版本的General Update Pattern
进行介绍。举一个简单的例子:
假设目前已有数据[‘a’, ‘b’, ‘c’….]等字母序列,现在希望通过D3,使用SVG将其呈现在页面上
V4
通过selection.enter()
, selection.exit()
与 selection
(update)分别指定相应逻辑,内容如下:
1 | const d3Pattern = (dataSet) => { |
V5
d3 V5.8.0 引入了一个新的API, selection.join
这个API的优势在于,对于一些比较简单、不需要特殊定义enter\exit过程的动作的d3图形,可以简化代码,以上的代码,使用V5的版本写,即
1 | const d3Pattern = (dataSet) => { |
可以发现,使用selection.join()
, 并不需要再手动写selection.exit().remove()
,这是因为selection.join()
这个函数默认的exit()
函数已经帮你写好了,该API下,d3的Update Pattern可以写为
1 | selection.join( |
当然,这个API的好处在于,一般的使用场景下(不需要在enter、exit等加特殊的动画或操作),完全可以简写,比如:
1 | svg.selectAll("circle") |
以上的写法,完全等价于
1 | svg.selectAll("circle") |
等价于V4版本的
1 | circles = svg.selectAll('circle') |
当然,V5完全兼容V4的update Pattern,无论是V4还是V5的新版API,这种Update Pattern的本质没有变,D3仍然是数据绑定,enter/update/exit的工作模式。
Pattern中的key
当使用d3.data()
绑定数据和dom时,相对应的关系,可能第一个元素对应第一个dom,第二元素对应第二dom等; 但当Array
发生变化时,比如重新排序、插入等操作,这时候,数组中元素可能与dom的绑定关系就发生了一些微妙的变化。
最直观的例子就比如动态改变字符的例子
如图,发现新增的字符总是排在最后,实际上,如果数据一致保持和dom绑定的话,理论随机生成新字符,完全应该有机会出现在中间的。
在数据绑定时,传入一个唯一的key值,即可避免这种情况发生
1 | selection.data(data, d => d.id) |
完成以上步骤,只要定时的调用函数d3Pattern
,传入不同的data,即可实现上图的效果
Transition
好了,前面铺垫了这么多,终于到了主角d3.transition
了,但实际上,与之相关API屈指可数,想要让d3画出带交互和炫酷过渡效果的,重点还是对Update Pattern理解透彻。
基本动画使用
transition
的使用,与jquery十分类似,使用时,只需要对选择的元素调用,并指定修改的属性即可,即selection.transition().attr(...)
比如现在画布上有一个方块,该元素为rect
,我想要使其位置从默认的地方,到30位置,并加上动画,代码为
1 | rect.transition() |
效果如下
动画的基本使用,就是如此简单,下面简单看下相关的api
方法 | 描述 |
---|---|
selection.transition() | this schedules a transition for the selected elements |
transition.duration() | duration specifies the animation duration in milliseconds for each element |
transition.ease() | ease specifies the easing function, example: linear, elastic, bounce |
transition.delay() | delay specifies the delay in animation in milliseconds for each element |
这里注意d3的api都支持链式调用,因此比如上面的例子,希望将动画时间设置为1s,可以
1 | rect.transition() |
同理,ease和delay可以分别设置动画曲线和延迟。
Update Pattern下的动画
回到最开始的例子,这里用V4版本的Update Pattern举例
因为transition是应用在selection
上的,所以为了方便使用,我们可以先定义好动画
1 | const t = d3.transtion().duration(750) |
接下来,我们希望新加入的文字从上面掉下来,且位置更新时,能有一个动画效果,这时候需要设置在enter()
时,位置有一个从上倒下的过程(transtion)
1 | const d3Pattern = (dataSet) => { |
可以看到,原来呆板的样式,已经变的灵动多了。 当然,也可以继续为退出(exit)的文字,加上红色,与掉落的动画,让整体更具有动效,只需要对exit的部分做相应的处理:
1 | text.exit() |
如图,这是加了向下掉落和透明度变化的动画效果。
实战应用
比如现在已经有一个静态的柱状图,希望在鼠标hover的时候,有一些动态效果变化,如下图
对于柱状图的实现,这里就不赘述,这里解释下核心代码,思路与上面提到的完全相同:
监听鼠标移入事件
- 选择当前的bar,通过transition修改属性
- 监听鼠标移出
- 选择当前bar,鼠标移出,恢复属性
核心代码如下:
1 | svgElement |
这个柱状图的源码与教程出自D3.js Tutorial: Building Interactive Bar Charts with JavaScript
插值动画
对于一些特殊的过渡,比如颜色的变化、数字的跳变等,如果没有插值函数,直接使用transition().attr()
是无法实现的。
因此,d3提供了插值函数和插值动画的接口用于这类动画实现。当然,对于大多数场景,非差值动画都可满足了。
特殊的插值
对于一些常用的属性插值,d3提供了非常方便入口,分别是attrTween
(属性插值)/styleTween
(样式插值)/textTween
文字插值
这类插值主要用于比如颜色、线条粗细等“属性”差值,可以使用attrTween()
和styleTween
,对于数字变化,连续跳变,可以使用textTween
他们的用法类似,如下:
1 | //颜色插值,从红色变为蓝色 |
插值函数的第一个参数,是要修改的内容或属性,功能类似transition().attr()
里,attr的内容;第二个参数是返回的插值函数,可以使用d3提供的一些插值函数,当然也可以自定义插值函数。
举个简单的例子,比如想要实现一下效果:
只需要对元素添加鼠标事件,并通过以上的插值函数完成即可
1 | svg.append('text') |
接下来说下自定义函数,比如仍然是红色变为蓝色,我们可以在插值函数返回自己定义的函数func(t)
, 该函数会在动画时间内不断的运行,t为[0, 1],借助这个思路,以上的效果可以用自定义函数实现如下:
1 | svg.append('text') |
以上两种方案,均可以实现动图的效果哦。
可以看到,对于插值动画,核心在于插值内容的产生。d3提供了多款插值,相关的列表如下,比如在使用数字跳变动画时,就可以使用d3.interpolatorRound(start,end)
来产生整形的数字插值; d3.interpolateRgb(color, color2)
来产生颜色插值等,具体的插值函数用法可查阅相关API。
d3.interpolatNumber
d3.interpolatRound
d3.interpolatString
d3.interpolatRgb
d3.interpolatHsl
d3.interpolatLab
d3.interpolatHcl
d3.interpolatArray
d3.interpolatObject
d3.interpolatTransform
d3.interpolatZoom
通用插值
当然,除了前面提到的API,还有一个更通用的产值函数API,d3.tween()
同attrTween()
等类似,它的第二个参数也是传入插值函数;不同的是,第一个参数,可以传入更通用的想要改变的内容,比如同样是上面的fill
属性,使用通用插值函数的写法就是:
1 | selection.transition() |
于是我们发现,其实通用API与前面的特殊的三个API用法及其类似,唯一不同的就是通用API的第一个参数可以接受更广泛的变更属性。
这里就不多举例了,关于插值函数的一些参考实例可以在这里查看